//
//  StringExtension.m
//  CocoaMoney
//
//  Created by Daniel Braun on 29/09/06.
//  Copyright 2006 Daniel Braun http://braun.daniel.free.fr. All rights reserved.
//

#import "StringExtension.h"
#import "objcExt.h"

@implementation NSString (StringExtension)
- (NSString *) stringBetween:(NSString *)stringBefore and:(NSString *)stringAfter inclusive:(BOOL) incl
{
	NSRange r1, r2;
	int l=[self length];
	if (!l) return nil;
	if (stringBefore) {
		r1=[self rangeOfString:stringBefore options:NSCaseInsensitiveSearch];
		if (NSNotFound==r1.location) return nil;
		if (!incl) r1.location+=r1.length;
		r1.length=l-r1.location;
	} else {
		r1.location=0;
		r1.length=l;
	}
	if (stringAfter) {
		r2=[self rangeOfString:stringAfter options:NSCaseInsensitiveSearch range:r1];
		if (NSNotFound==r2.location) return nil;
		r1.length=r2.location-r1.location;
		if (incl) r1.length += r2.length;
	}
	NSString *v=[self substringWithRange:r1];
	if (0) NSLog(@"extracted substring %@\n", v);
	return v;
}

	
- (double) doubleSmartValueFound:(BOOL *)pf
{
	char szTmp[1024];
	char *pdot, *pvirg;
	char thpoint='\0';
	char decimalpoint='\0';
	const char *str=[self UTF8String];
	if (0) NSLog(@"convert %s\n", str);
	pdot=strchr(str, '.');
	pvirg=strchr(str, ',');
	const char *p;
	const char *ldigit=NULL;
	const char *fdigit=NULL;
	if (pf) *pf=NO;
	for (p=str; *p; p++) {
		if (isdigit(*p)) {
			if (pf) *pf=YES;
			ldigit=p;
			if (!fdigit) fdigit=p;
		}
	}
	if (0) NSLog(@"convert %s ldigit=%p fdigit=%p\n", str, ldigit, fdigit);
	if (!ldigit) return 0.0;
	const char *tfdigit=fdigit;
	while (tfdigit>=str) {
		if (' '==tfdigit[-1]) {
			tfdigit--;
			continue;
		}
		if ('-'==tfdigit[-1]) {
			fdigit=tfdigit-1;
		}
		break;
	}
	if (pdot>ldigit) pdot=0;
	if (pvirg>ldigit) pvirg=0;
	if (pdot>pvirg) {
		decimalpoint='.';
		thpoint=',';
	} else {
		decimalpoint=',';
		thpoint='.';
	}
	if (ldigit-fdigit>=sizeof(szTmp)-1) {
		return 0.0;
	}
	int i;
	for (i=0, p=fdigit; p<=ldigit; p++) {
		if (' '==*p) {
		} else if (thpoint==*p) {
		} else if (isdigit(*p) || (('-'==*p) && !i)) {
			szTmp[i++]=*p;
		} else if (' '==*p) {
		} else if (decimalpoint==*p) {
			szTmp[i++]='.';
		} else break;
	}
	szTmp[i]='\0';
	if (0) NSLog(@"converted %s to %s\n", str, szTmp);
	return atof(szTmp);
}
- (double) doubleSmartValue
{
	return [self doubleSmartValueFound:NULL];
}

- (NSNumber *) numberSmartValue
{
	BOOL f=NO;
	double v;
	v=[self doubleSmartValueFound:&f];
	if (f) return [NSNumber numberWithDouble:v];
	return nil;
}

- (NSString *) nonNumPrefix
{
	const char *str=[self UTF8String];
	BOOL f=NO;
	int i;
	for (i=0; str[i]; i++) {
		if (isdigit(str[i])) {
			f=YES;
			break;
		}
	}
	if (!f) return self;
	if (i>1) {
		if ((str[i-1]==' ') && (str[i-2]=='-')) i-=2;
		else if (str[i-1]=='-') i-=1;
	} else if (i>0) {
		if (str[i-1]=='-') i-=1;
	}
	NSRange r;
	r.location=0;
	r.length=i;
	if (!i) return nil;
	return [self substringWithRange:r];
}
- (NSRange) lastNumberRange
{
	NSRange r;
	r.location=0;
	r.length=0;
	const char *str=[self UTF8String];
	int l=[self length];
	if (!l) return r;
	int p1=0;
	int p2=0;
	int i;
	for (i=l-1; i>=0; i--) {
		if (isdigit(str[i])) {
			p2=i;
			break;
		}
	}
	for (i=p2-1; i>=0; i--) {
		if (!isdigit(str[i])) {
			p1=i+1;
			break;
		}
	}
	r.location=p1;
	r.length=p2-p1+1;
	return r;
}

- (BOOL) startsWithNumber
{
	if (![self length]) return NO;
	NSRange r=[self firstNumberRange];
	if (!r.length) return NO;
	if (r.location==0) return YES;
	return NO;
}
- (NSRange) firstNumberRange
{
	NSRange r;
	r.location=0;
	r.length=0;
	const char *str=[self cStringUsingEncoding:NSISOLatin1StringEncoding];
	int l=[self length];
	if (!str || !l) {
		return r;
	}
	int p1=l;
	int p2=0;
	int i;
	for (i=0; i<l; i++) {
		if (isdigit(str[i])) {
			p2=i;
			break;
		}
	}
	p1=p2;
	for (i=p2+1; i<l; i++) {
		if (!isdigit(str[i])) {
			p1=i;
			break;
		}
	}
	if ((i==l) &&(p1!=p2)) p1=l;
	r.location=p2;
	r.length=p1-p2;
	return r;
}

- (NSString *) nonNumPostfix;
{
	const char *str=[self UTF8String];
	int l=[self length];
	BOOL f=NO;
	int i;
	for (i=l-1; i>=0; i--) {
		if (isdigit(str[i])) {
			f=YES;
			break;
		}
	}
	if (!f) return self;
	if (i==l-1) return @"";
	NSRange r;
	r.location=i+1;
	r.length=l-i-1;
	return [self substringWithRange:r];
}

- (float) nearString:(NSString *)s
{
	/* Levenshtein distance
	 * http://en.wikipedia.org/wiki/Levenshtein_distance
	 */
	unsigned int n1 = [self length];
	unsigned int n2 = [s length];
	if (!n1) return 0.0;
	if (!n2) return 0.0;
	const char *s1=[self cStringUsingEncoding:NSUTF8StringEncoding];
	const char *s2=[s cStringUsingEncoding:NSUTF8StringEncoding];
	const unsigned int cost_del = 1;
	const unsigned int cost_ins = 1;
	const unsigned int cost_sub = 1;
	
	unsigned int *p = calloc(sizeof(int), n2+1);
	unsigned int* q = calloc(sizeof(int), n2+1);
	unsigned int* r;
		
	p[0] = 0;
	int i,j;
	for (j=1; j<=n2; j++) {
		p[j] = p[j-1] + cost_ins;
	}
	for(i = 1; i <= n1; ++i ) {
		q[0] = p[0] + cost_del;
		for(j=1; j <= n2; ++j ) {
			unsigned int d_del = p[j] + cost_del;
			unsigned int d_ins = q[j-1] + cost_ins;
			unsigned int d_sub = p[j-1] + ( s1[i-1] == s2[j-1] ? 0 : cost_sub );
			q[j] = MIN(MIN( d_del, d_ins ), d_sub );
		}
		r = p;
		p = q;
		q = r;
	}
		
	unsigned int tmp = p[n2];
	free(p);
	free(q);
	// tmp is Levenshtein distance
 	// transform to a matching result 1.0=identical string, 0.0=very different
	int l=MAX(n1, n2);
	float res=1.0-tmp/(1.0*l);
	return res;
}

- (BOOL) hasSubString:(NSString *)s
{
	NSRange r;
	r=[self rangeOfString:s];
	if (0==r.length) return NO;
	return YES;
}
- (BOOL) findOneSubstring:(NSArray *)substrings range:(NSRange *)outRange index:(int *)pi
{
	NSRange br;
	BOOL haveOne=NO;
	unsigned int i, count = [substrings count];
	for (i = 0; i < count; i++) {
		NSString * sub = [substrings objectAtIndex:i];
		NSString *sb=[sub lowercaseStringRemovingAccentsAndPunct];
		//NSLog(@"testing wit <%@>\n", sb);
		NSRange r=[self rangeOfString:sb];
		if (!r.length) continue;
		if ((!haveOne) ||
		    (r.location<br.location) ||
		    ((r.location==br.location)&&(r.length>br.length)) ) {
			haveOne=YES;
			br=r;
			if (pi) *pi=i;
			continue;
		}
	}
	if (haveOne && outRange) *outRange=br;
	if (haveOne && pi) {
		//NSLog(@"found idx %d (%@) for %@\n",*pi, [substrings objectAtIndex:*pi], self);
	} else if (!haveOne) {
		//NSLog(@"no found for %@\n", self);
	}
	return haveOne;
}

- (BOOL) isIdentifier
{
	const char *s=[self UTF8String];
	if ((*s>='A') && (*s<='Z')) return YES;
	if ((*s>='a') && (*s<='z')) return YES;
	if (*s=='_') return YES;
	return NO;
}

- (NSString *) convertToASPath
{
	NSString *mp;
	mp=[self stringByStandardizingPath];
	NSArray *pc=[mp pathComponents];
	NSRange r;
	r.location=1;
	r.length=[pc count]-1;
	pc=[pc subarrayWithRange:r];
	mp=[pc componentsJoinedByString:@":"];
	return mp;
}

- (NSString *) stringByReplacing:(NSString *)s by:(NSString *)s2
{
	NSMutableString *r;
	r=[[self mutableCopy]autorelease];
	NSRange rg;
	rg.location=0;
	rg.length=[self length];
	[r replaceOccurrencesOfString:s withString:s2 options:0 range:rg];
	return r;
}




- (NSString *) parseWithActions:(NSDictionary *)act for:(id)performer
	   andSimpleReplacement:(NSDictionary *)sr
{
	NSString *r;
	NSString *e;
	unsigned int par=0;
	r=@"";
	NSArray *ar=[self componentsSeparatedByString:@"$"];
	//printf("got %d comp\n", [ar count]);
	unsigned int i, count = [ar count];
	for (i = 0; i < count; i++) {
		e = [ar objectAtIndex:i];
		if ((i%2)==par) {
			r=[r stringByAppendingString:e];
			continue;
		}
		NSString *keyword;
		NSString *paramlist=nil;
		NSMutableDictionary *params=[NSMutableDictionary dictionaryWithCapacity:5];
		NSArray *p1;
		p1=[e componentsSeparatedByString:@"{"];
		keyword=[p1 objectAtIndex:0];
		if ([p1 count]>1) {
			paramlist=[p1 objectAtIndex:1];
			p1=[paramlist componentsSeparatedByString:@"}"];
			paramlist=[p1 objectAtIndex:0];
			p1=[paramlist componentsSeparatedByString:@","];
			unsigned int j, np = [p1 count];
			for (j = 0; j < np; j++) {
				NSString * ps = [p1 objectAtIndex:j];
				NSArray *p2=[ps componentsSeparatedByString:@"="];
				NSString *v;
				if ([p2 count]>1) v=[p2 objectAtIndex:1];
				else v=@"yes";
				[params setObject:v forKey:[p2 objectAtIndex:0]];
			}
		} 
		NSString *rep=nil;

		if ([keyword isEqualToString:@"copyright"]) {
		} else {
			NSString *va=[act objectForKey:keyword];
			if (va) {
				SEL s=selFromString(va);
				if (s) {
					rep=[performer performSelector:s withObject:params];
				}
			}
			if (!rep) {
				rep=[sr objectForKey:keyword];
			}
		}
		if (rep) {
			if (![rep isKindOfClass:[NSString class]]) rep=[rep description];
			r=[r stringByAppendingString:rep];
		}
	}
	return r;
}

+ (id) stringFromResourceFile:(NSString *)filename
{
	NSBundle *mb=[NSBundle mainBundle];
	NSString *p=[mb pathForResource:filename ofType:nil];
	if (!p) return nil;
	NSError *err=nil;
	NSString *s=[NSString stringWithContentsOfFile:p 
					      encoding:NSISOLatin1StringEncoding
						 error:&err];
	if (!s) NSLog(@"error reading %@ : %@\n", p, err);
	return s;
}

- (NSCalendarDate *) possibleDate:(NSRange *)rg
{
	const char *s=[self UTF8String];
	// look for d[d]/d[d]/[dd]dd, where d is digit, '/' is ponctuation, same char
	int st=0;
	int sep=-1;
	const char *p;
	char *p1=nil;
	BOOL ok=NO;
	int m;
	int d;
	int y;
	for (p=s; !ok; p++) {
		switch (st) {
			case 0: // skip to digit
				if (isdigit(*p)) {
					st=1;
					p1=(char *) p;
					m=0; y=0;
					d=*p-'0';
				}
				break;
			case 1: // got 1st digit
				if (isdigit(*p)) {
					st=2;
					d=d*10+*p-'0';
				}
				else if (!isalpha(*p)) {
					sep=*p;
					st=3;
				} else st=0;
				break;
			case 2:
				if (!isalpha(*p)) {
					sep=*p;
					st=3;
				} else st=0;
				break;
			case 3: 
				if (isdigit(*p)) {
					st=4;
					m=*p-'0';
				} else st=0;
				break;
			case 4:
				if (sep==*p) {
					sep=*p;
					st=6;
				} else if (isdigit(*p)) {
					st=5;
					m=m*10+*p-'0';
				}
				else st=0;
				break;
			case 5: 
				if (sep==*p) {
					sep=*p;
					st=6;
				} else  st=0;
				break;
			case 6:
				if (isdigit(*p)) {
					st++;
					y=*p-'0';
				}
				else st=0;
				break;
			case 7:
				if (isdigit(*p)) {
					st++;
					y=y*10+*p-'0';
				}
				else st=0;
				break;
			case 8:
				if (isdigit(*p)){
					st++;
					y=y*10+*p-'0';
				}
				else {
					ok=YES;
				}
				break;
			case 9:
				if (isdigit(*p)) {
					st++;
					ok=YES;
					y=y*10+*p-'0';
				}
				else st=0;
				break;
		}
		if (!*p) break;
	}
	if (!ok) return nil;
	if (y<100) y+=1900;
	NSCalendarDate *rd=[NSCalendarDate dateWithYear:y month:m day:d 
						   hour:12 minute:0 second:0 timeZone:nil];
	if (rg) {
		rg->location=p1-s;
		rg->length=p-p1;
	}
	return rd;
}

@end
@implementation NSMutableString (StringExtension) 

- (void) deleteLastCharacters:(int)numtodelete
{
	if (!numtodelete) return;
	int l=[self length];
	NSRange r;
	if (numtodelete>l) numtodelete=l;
	r.location=l-numtodelete;
	r.length=numtodelete;
	[self deleteCharactersInRange:r];
}

@end



@implementation NSString (removeAccents)
- (NSString*) lowercaseStringRemovingAccents 
{
	return [[[NSString alloc] initWithData:[[self lowercaseString] dataUsingEncoding:NSASCIIStringEncoding 
								    allowLossyConversion:YES]  
				      encoding:NSASCIIStringEncoding] autorelease];
}
- (NSString*) lowercaseStringRemovingAccentsAndPunct
{
	NSString *s= [[[NSString alloc] initWithData:[[self lowercaseString] dataUsingEncoding:NSASCIIStringEncoding 
								    allowLossyConversion:YES]  
				      encoding:NSASCIIStringEncoding] autorelease];
	const char *str=[s UTF8String];
	int l=[s length];
	int i;
	for (i=l-1; i>=0; i--) {
		if (isalnum(str[i])) break;
	}
	return [s substringToIndex:i+1];
}	
@end

